1   /*
2    * reserved comment block
3    * DO NOT REMOVE OR ALTER!
4    */
5   /*
6    * Copyright  1999-2004 The Apache Software Foundation.
7    *
8    *  Licensed under the Apache License, Version 2.0 (the "License");
9    *  you may not use this file except in compliance with the License.
10   *  You may obtain a copy of the License at
11   *
12   *      http://www.apache.org/licenses/LICENSE-2.0
13   *
14   *  Unless required by applicable law or agreed to in writing, software
15   *  distributed under the License is distributed on an "AS IS" BASIS,
16   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   *  See the License for the specific language governing permissions and
18   *  limitations under the License.
19   *
20   */
21  package com.sun.org.apache.xml.internal.security.utils;
22  
23  
24  
25  import java.io.IOException;
26  import java.io.OutputStream;
27  import java.security.AccessController;
28  import java.security.PrivilegedAction;
29  import java.util.HashMap;
30  import java.util.HashSet;
31  import java.util.Iterator;
32  import java.util.Map;
33  import java.util.Set;
34  
35  import com.sun.org.apache.xml.internal.security.c14n.CanonicalizationException;
36  import com.sun.org.apache.xml.internal.security.c14n.Canonicalizer;
37  import com.sun.org.apache.xml.internal.security.c14n.InvalidCanonicalizerException;
38  import org.w3c.dom.Attr;
39  import org.w3c.dom.Document;
40  import org.w3c.dom.Element;
41  import org.w3c.dom.NamedNodeMap;
42  import org.w3c.dom.Node;
43  import org.w3c.dom.NodeList;
44  import org.w3c.dom.Text;
45  
46  
47  
48  /**
49   * DOM and XML accessibility and comfort functions.
50   *
51   * @author Christian Geuer-Pollmann
52   */
53  public class XMLUtils {
54  
55     private static boolean ignoreLineBreaks =
56        AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
57           public Boolean run() {
58              return Boolean.getBoolean
59                 ("com.sun.org.apache.xml.internal.security.ignoreLineBreaks");
60           }
61        });
62  
63     /**
64      * Constructor XMLUtils
65      *
66      */
67     private XMLUtils() {
68  
69        // we don't allow instantiation
70     }
71     public static Element getNextElement(Node el) {
72             while ((el!=null) && (el.getNodeType()!=Node.ELEMENT_NODE)) {
73                     el=el.getNextSibling();
74             }
75             return (Element)el;
76  
77     }
78  
79     /**
80      * @param rootNode
81      * @param result
82      * @param exclude
83      * @param com wheather comments or not
84      */
85     public static void getSet(Node rootNode,Set result,Node exclude ,boolean com) {
86            if ((exclude!=null) && isDescendantOrSelf(exclude,rootNode)){
87                  return;
88        }
89        getSetRec(rootNode,result,exclude,com);
90     }
91     static final void getSetRec(final Node rootNode,final Set result,
92          final Node exclude ,final boolean com) {
93             //Set result = new HashSet();
94         if (rootNode==exclude) {
95            return;
96         }
97             switch (rootNode.getNodeType()) {
98                  case Node.ELEMENT_NODE:
99                                  result.add(rootNode);
100                         Element el=(Element)rootNode;
101                 if (el.hasAttributes()) {
102                                 NamedNodeMap nl = ((Element)rootNode).getAttributes();
103                                 for (int i=0;i<nl.getLength();i++) {
104                                         result.add(nl.item(i));
105                                 }
106                 }
107                 //no return keep working
108                 case Node.DOCUMENT_NODE:
109                                 for (Node r=rootNode.getFirstChild();r!=null;r=r.getNextSibling()){
110                                         if (r.getNodeType()==Node.TEXT_NODE) {
111                                                 result.add(r);
112                                                 while ((r!=null) && (r.getNodeType()==Node.TEXT_NODE)) {
113                                                         r=r.getNextSibling();
114                                                 }
115                                                 if (r==null)
116                                                         return;
117                                         }
118                                         getSetRec(r,result,exclude,com);
119                                 }
120                                 return;
121                         case Node.COMMENT_NODE:
122                                 if (com) {
123                                         result.add(rootNode);
124                                 }
125                             return;
126                         case Node.DOCUMENT_TYPE_NODE:
127                                 return;
128                         default:
129                                 result.add(rootNode);
130            }
131            return;
132    }
133 
134 
135    /**
136     * Outputs a DOM tree to an {@link OutputStream}.
137     *
138     * @param contextNode root node of the DOM tree
139     * @param os the {@link OutputStream}
140     */
141    public static void outputDOM(Node contextNode, OutputStream os) {
142       XMLUtils.outputDOM(contextNode, os, false);
143    }
144 
145    /**
146     * Outputs a DOM tree to an {@link OutputStream}. <I>If an Exception is
147     * thrown during execution, it's StackTrace is output to System.out, but the
148     * Exception is not re-thrown.</I>
149     *
150     * @param contextNode root node of the DOM tree
151     * @param os the {@link OutputStream}
152     * @param addPreamble
153     */
154    public static void outputDOM(Node contextNode, OutputStream os,
155                                 boolean addPreamble) {
156 
157       try {
158          if (addPreamble) {
159             os.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n".getBytes());
160          }
161 
162          os.write(
163             Canonicalizer.getInstance(
164                Canonicalizer.ALGO_ID_C14N_WITH_COMMENTS).canonicalizeSubtree(
165                contextNode));
166       } catch (IOException ex) {}
167       catch (InvalidCanonicalizerException ex) {
168          ex.printStackTrace();
169       } catch (CanonicalizationException ex) {
170          ex.printStackTrace();
171       }
172    }
173 
174    /**
175     * Serializes the <CODE>contextNode</CODE> into the OutputStream, <I>but
176     * supresses all Exceptions</I>.
177     * <BR />
178     * NOTE: <I>This should only be used for debugging purposes,
179     * NOT in a production environment; this method ignores all exceptions,
180     * so you won't notice if something goes wrong. If you're asking what is to
181     * be used in a production environment, simply use the code inside the
182     * <code>try{}</code> statement, but handle the Exceptions appropriately.</I>
183     *
184     * @param contextNode
185     * @param os
186     */
187    public static void outputDOMc14nWithComments(Node contextNode,
188            OutputStream os) {
189 
190       try {
191          os.write(
192             Canonicalizer.getInstance(
193                Canonicalizer.ALGO_ID_C14N_WITH_COMMENTS).canonicalizeSubtree(
194                contextNode));
195       } catch (IOException ex) {
196 
197          // throw new RuntimeException(ex.getMessage());
198       } catch (InvalidCanonicalizerException ex) {
199 
200          // throw new RuntimeException(ex.getMessage());
201       } catch (CanonicalizationException ex) {
202 
203          // throw new RuntimeException(ex.getMessage());
204       }
205    }
206 
207 
208    /**
209     * Method getFullTextChildrenFromElement
210     *
211     * @param element
212     * @return the string of chi;ds
213     */
214    public static String getFullTextChildrenFromElement(Element element) {
215 
216       StringBuffer sb = new StringBuffer();
217       NodeList children = element.getChildNodes();
218       int iMax = children.getLength();
219 
220       for (int i = 0; i < iMax; i++) {
221          Node curr = children.item(i);
222 
223          if (curr.getNodeType() == Node.TEXT_NODE) {
224             sb.append(((Text) curr).getData());
225          }
226       }
227 
228       return sb.toString();
229    }
230 
231 
232    static  String dsPrefix=null;
233    static Map namePrefixes=new HashMap();
234    /**
235     * Creates an Element in the XML Signature specification namespace.
236     *
237     * @param doc the factory Document
238     * @param elementName the local name of the Element
239     * @return the Element
240     */
241    public static Element createElementInSignatureSpace(Document doc,
242            String elementName) {
243 
244       if (doc == null) {
245          throw new RuntimeException("Document is null");
246       }
247 
248       if ((dsPrefix == null) || (dsPrefix.length() == 0)) {
249          return doc.createElementNS(Constants.SignatureSpecNS, elementName);
250       }
251       String namePrefix=(String) namePrefixes.get(elementName);
252       if (namePrefix==null) {
253           StringBuffer tag=new StringBuffer(dsPrefix);
254           tag.append(':');
255           tag.append(elementName);
256           namePrefix=tag.toString();
257           namePrefixes.put(elementName,namePrefix);
258       }
259       return doc.createElementNS(Constants.SignatureSpecNS, namePrefix);
260    }
261 
262    /**
263     * Returns true if the element is in XML Signature namespace and the local
264     * name equals the supplied one.
265     *
266     * @param element
267     * @param localName
268     * @return true if the element is in XML Signature namespace and the local name equals the supplied one
269     */
270    public static boolean elementIsInSignatureSpace(Element element,
271            String localName) {
272       return ElementProxy.checker.isNamespaceElement(element, localName, Constants.SignatureSpecNS);
273    }
274 
275    /**
276     * Returns true if the element is in XML Encryption namespace and the local
277     * name equals the supplied one.
278     *
279     * @param element
280     * @param localName
281     * @return true if the element is in XML Encryption namespace and the local name equals the supplied one
282     */
283    public static boolean elementIsInEncryptionSpace(Element element,
284            String localName) {
285            return ElementProxy.checker.isNamespaceElement(element, localName, EncryptionConstants.EncryptionSpecNS);
286    }
287 
288    /**
289     * This method returns the owner document of a particular node.
290     * This method is necessary because it <I>always</I> returns a
291     * {@link Document}. {@link Node#getOwnerDocument} returns <CODE>null</CODE>
292     * if the {@link Node} is a {@link Document}.
293     *
294     * @param node
295     * @return the owner document of the node
296     */
297    public static Document getOwnerDocument(Node node) {
298 
299       if (node.getNodeType() == Node.DOCUMENT_NODE) {
300          return (Document) node;
301       }
302          try {
303             return node.getOwnerDocument();
304          } catch (NullPointerException npe) {
305             throw new NullPointerException(I18n.translate("endorsed.jdk1.4.0")
306                                            + " Original message was \""
307                                            + npe.getMessage() + "\"");
308          }
309 
310    }
311 
312     /**
313      * This method returns the first non-null owner document of the Node's in this Set.
314      * This method is necessary because it <I>always</I> returns a
315      * {@link Document}. {@link Node#getOwnerDocument} returns <CODE>null</CODE>
316      * if the {@link Node} is a {@link Document}.
317      *
318      * @param xpathNodeSet
319      * @return the owner document
320      */
321     public static Document getOwnerDocument(Set xpathNodeSet) {
322        NullPointerException npe = null;
323        Iterator iterator = xpathNodeSet.iterator();
324        while(iterator.hasNext()) {
325            Node node = (Node) iterator.next();
326            int nodeType =node.getNodeType();
327            if (nodeType == Node.DOCUMENT_NODE) {
328               return (Document) node;
329            }
330               try {
331                  if (nodeType==Node.ATTRIBUTE_NODE) {
332                     return ((Attr)node).getOwnerElement().getOwnerDocument();
333                  }
334                  return node.getOwnerDocument();
335               } catch (NullPointerException e) {
336                   npe = e;
337               }
338 
339        }
340        throw new NullPointerException(I18n.translate("endorsed.jdk1.4.0")
341                                        + " Original message was \""
342                                        + (npe == null ? "" : npe.getMessage()) + "\"");
343     }
344 
345     /**
346      * Method createDSctx
347      *
348      * @param doc
349      * @param prefix
350      * @param namespace
351      * @return the element.
352      */
353     public static Element createDSctx(Document doc, String prefix,
354                                       String namespace) {
355 
356        if ((prefix == null) || (prefix.trim().length() == 0)) {
357           throw new IllegalArgumentException("You must supply a prefix");
358        }
359 
360        Element ctx = doc.createElementNS(null, "namespaceContext");
361 
362        ctx.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:" + prefix.trim(),
363                           namespace);
364 
365        return ctx;
366     }
367 
368    /**
369     * Method addReturnToElement
370     *
371     * @param e
372     */
373    public static void addReturnToElement(Element e) {
374 
375       if (!ignoreLineBreaks) {
376          Document doc = e.getOwnerDocument();
377          e.appendChild(doc.createTextNode("\n"));
378       }
379    }
380 
381    public static void addReturnToElement(Document doc, HelperNodeList nl) {
382       if (!ignoreLineBreaks) {
383          nl.appendChild(doc.createTextNode("\n"));
384       }
385    }
386 
387    public static void addReturnBeforeChild(Element e, Node child) {
388       if (!ignoreLineBreaks) {
389          Document doc = e.getOwnerDocument();
390          e.insertBefore(doc.createTextNode("\n"), child);
391       }
392    }
393 
394    /**
395     * Method convertNodelistToSet
396     *
397     * @param xpathNodeSet
398     * @return the set with the nodelist
399     */
400    public static Set convertNodelistToSet(NodeList xpathNodeSet) {
401 
402       if (xpathNodeSet == null) {
403          return new HashSet();
404       }
405 
406       int length = xpathNodeSet.getLength();
407       Set set = new HashSet(length);
408 
409       for (int i = 0; i < length; i++) {
410          set.add(xpathNodeSet.item(i));
411       }
412 
413       return set;
414    }
415 
416 
417    /**
418     * This method spreads all namespace attributes in a DOM document to their
419     * children. This is needed because the XML Signature XPath transform
420     * must evaluate the XPath against all nodes in the input, even against
421     * XPath namespace nodes. Through a bug in XalanJ2, the namespace nodes are
422     * not fully visible in the Xalan XPath model, so we have to do this by
423     * hand in DOM spaces so that the nodes become visible in XPath space.
424     *
425     * @param doc
426     * @see <A HREF="http://nagoya.apache.org/bugzilla/show_bug.cgi?id=2650">Namespace axis resolution is not XPath compliant </A>
427     */
428    public static void circumventBug2650(Document doc) {
429 
430       Element documentElement = doc.getDocumentElement();
431 
432       // if the document element has no xmlns definition, we add xmlns=""
433       Attr xmlnsAttr =
434          documentElement.getAttributeNodeNS(Constants.NamespaceSpecNS, "xmlns");
435 
436       if (xmlnsAttr == null) {
437          documentElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns", "");
438       }
439 
440       XMLUtils.circumventBug2650internal(doc);
441    }
442 
443    /**
444     * This is the work horse for {@link #circumventBug2650}.
445     *
446     * @param node
447     * @see <A HREF="http://nagoya.apache.org/bugzilla/show_bug.cgi?id=2650">Namespace axis resolution is not XPath compliant </A>
448     */
449    private static void circumventBug2650internal(Node node) {
450            Node parent=null;
451            Node sibling=null;
452            final String namespaceNs=Constants.NamespaceSpecNS;
453            do {
454          switch (node.getNodeType()) {
455          case Node.ELEMENT_NODE :
456                  Element element = (Element) node;
457              if (!element.hasChildNodes())
458                  break;
459              if (element.hasAttributes()) {
460              NamedNodeMap attributes = element.getAttributes();
461              int attributesLength = attributes.getLength();
462 
463              for (Node child = element.getFirstChild(); child!=null;
464                 child=child.getNextSibling()) {
465 
466                 if (child.getNodeType() != Node.ELEMENT_NODE) {
467                         continue;
468                 }
469                 Element childElement = (Element) child;
470 
471                 for (int i = 0; i < attributesLength; i++) {
472                         Attr currentAttr = (Attr) attributes.item(i);
473                         if (namespaceNs!=currentAttr.getNamespaceURI())
474                                 continue;
475                         if (childElement.hasAttributeNS(namespaceNs,
476                                                         currentAttr.getLocalName())) {
477                                         continue;
478                         }
479                         childElement.setAttributeNS(namespaceNs,
480                                                 currentAttr.getName(),
481                                                 currentAttr.getNodeValue());
482 
483 
484                 }
485              }
486              }
487          case Node.ENTITY_REFERENCE_NODE :
488          case Node.DOCUMENT_NODE :
489                  parent=node;
490                  sibling=node.getFirstChild();
491              break;
492          }
493          while ((sibling==null) && (parent!=null)) {
494                          sibling=parent.getNextSibling();
495                          parent=parent.getParentNode();
496                  };
497        if (sibling==null) {
498                          return;
499                  }
500 
501          node=sibling;
502          sibling=node.getNextSibling();
503            } while (true);
504    }
505 
506    /**
507     * @param sibling
508     * @param nodeName
509     * @param number
510     * @return nodes with the constrain
511     */
512    public static Element selectDsNode(Node sibling, String nodeName, int number) {
513         while (sibling!=null) {
514                 if (ElementProxy.checker.isNamespaceElement(sibling, nodeName, Constants.SignatureSpecNS )) {
515                         if (number==0){
516                                 return (Element)sibling;
517                         }
518                         number--;
519                 }
520                 sibling=sibling.getNextSibling();
521         }
522         return null;
523    }
524 
525    /**
526     * @param sibling
527     * @param nodeName
528     * @param number
529     * @return nodes with the constrain
530     */
531 
532    public static Element selectXencNode(Node sibling, String nodeName, int number) {
533         while (sibling!=null) {
534                 if (ElementProxy.checker.isNamespaceElement(sibling, nodeName, EncryptionConstants.EncryptionSpecNS )) {
535                         if (number==0){
536                                 return (Element)sibling;
537                         }
538                         number--;
539                 }
540                 sibling=sibling.getNextSibling();
541         }
542         return null;
543    }
544 
545 
546    /**
547     * @param sibling
548     * @param nodeName
549     * @param number
550     * @return nodes with the constrain
551     */
552    public static Text selectDsNodeText(Node sibling, String nodeName, int number) {
553             Node n=selectDsNode(sibling,nodeName,number);
554         if (n==null) {
555                 return null;
556         }
557         n=n.getFirstChild();
558         while (n!=null && n.getNodeType()!=Node.TEXT_NODE) {
559                 n=n.getNextSibling();
560         }
561         return (Text)n;
562    }
563 
564    /**
565     * @param sibling
566     * @param uri
567     * @param nodeName
568     * @param number
569     * @return nodes with the constrain
570     */
571    public static Text selectNodeText(Node sibling, String uri, String nodeName, int number) {
572         Node n=selectNode(sibling,uri,nodeName,number);
573     if (n==null) {
574         return null;
575     }
576     n=n.getFirstChild();
577     while (n!=null && n.getNodeType()!=Node.TEXT_NODE) {
578         n=n.getNextSibling();
579     }
580     return (Text)n;
581    }
582 
583    /**
584     * @param sibling
585     * @param uri
586     * @param nodeName
587     * @param number
588     * @return nodes with the constrain
589     */
590    public static Element selectNode(Node sibling, String uri,String nodeName, int number) {
591         while (sibling!=null) {
592                 if (ElementProxy.checker.isNamespaceElement(sibling, nodeName, uri)) {
593                         if (number==0){
594                                 return (Element)sibling;
595                         }
596                         number--;
597                 }
598                 sibling=sibling.getNextSibling();
599         }
600         return null;
601    }
602 
603    /**
604     * @param sibling
605     * @param nodeName
606     * @return nodes with the constrain
607     */
608    public static Element[] selectDsNodes(Node sibling,String nodeName) {
609      return selectNodes(sibling,Constants.SignatureSpecNS,nodeName);
610    }
611    /**
612     * @param sibling
613     * @param uri
614     * @param nodeName
615     * @return nodes with the constrain
616     */
617     public static Element[] selectNodes(Node sibling,String uri,String nodeName) {
618         int size=20;
619         Element[] a= new Element[size];
620         int curr=0;
621         //List list=new ArrayList();
622         while (sibling!=null) {
623                 if (ElementProxy.checker.isNamespaceElement(sibling, nodeName, uri)) {
624                         a[curr++]=(Element)sibling;
625                         if (size<=curr) {
626                                 int cursize= size<<2;
627                                 Element []cp=new Element[cursize];
628                                 System.arraycopy(a,0,cp,0,size);
629                                 a=cp;
630                                 size=cursize;
631                         }
632                 }
633                 sibling=sibling.getNextSibling();
634         }
635         Element []af=new Element[curr];
636         System.arraycopy(a,0,af,0,curr);
637         return af;
638    }
639 
640    /**
641     * @param signatureElement
642     * @param inputSet
643     * @return nodes with the constrain
644     */
645     public static Set excludeNodeFromSet(Node signatureElement, Set inputSet) {
646           Set resultSet = new HashSet();
647           Iterator iterator = inputSet.iterator();
648 
649           while (iterator.hasNext()) {
650             Node inputNode = (Node) iterator.next();
651 
652             if (!XMLUtils
653                     .isDescendantOrSelf(signatureElement, inputNode)) {
654                resultSet.add(inputNode);
655             }
656          }
657          return resultSet;
658      }
659 
660    /**
661     * Returns true if the descendantOrSelf is on the descendant-or-self axis
662     * of the context node.
663     *
664     * @param ctx
665     * @param descendantOrSelf
666     * @return true if the node is descendant
667     */
668    static public boolean isDescendantOrSelf(Node ctx, Node descendantOrSelf) {
669 
670       if (ctx == descendantOrSelf) {
671          return true;
672       }
673 
674       Node parent = descendantOrSelf;
675 
676       while (true) {
677          if (parent == null) {
678             return false;
679          }
680 
681          if (parent == ctx) {
682             return true;
683          }
684 
685          if (parent.getNodeType() == Node.ATTRIBUTE_NODE) {
686             parent = ((Attr) parent).getOwnerElement();
687          } else {
688             parent = parent.getParentNode();
689          }
690       }
691    }
692 
693     public static boolean ignoreLineBreaks() {
694         return ignoreLineBreaks;
695     }
696 }